﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace MapLibrary.Utility
{
    public abstract class Pool<T> where T: IPoolable
    {
        protected const int DEFAULT_SIZE = 10;

        protected T[] instances;
        protected Stack<int> availableIndices;

        public Pool()
        {
        }

        protected void Initialize()
        {
            instances = new T[DEFAULT_SIZE];
            availableIndices = new Stack<int>();

            for (int i = 0; i < DEFAULT_SIZE; i++)
            {
                instances[i] = Construct();
                instances[i].PoolIndex = i;
                
                availableIndices.Push(i);
            }
        }

        /// <summary>
        /// Build a T element.
        /// </summary>
        /// <returns></returns>
        protected abstract T Construct();

        /// <summary>
        /// Execute any tasks that need to happen after producing a new
        /// T.  For example, suppose T was Sprite.  Then we need to register
        /// the Body of the sprite into the World.
        /// </summary>
        /// <param name="toEdit"></param>
        protected abstract void PostNew(T toEdit);

        /// <summary>
        /// Execute any tasks that need to happen after reclaiming a 
        /// T.  For example, suppose T was Sprite.  Then we need to remove
        /// the Sprite's body from World, and also remove the Sprite's
        /// controller.
        /// </summary>
        /// <param name="toEdit"></param>
        protected abstract void PostReclaim(T toEdit);

        /// <summary>
        /// Get a new object.  Reallocate if there aren't any more objects available.
        /// </summary>
        /// <returns></returns>
        public T New()
        {
            T toReturn;
            if (availableIndices.Count > 0)
            {
                int nextIndex = availableIndices.Pop();
                toReturn = instances[nextIndex];
                PostNew(toReturn);
            }
            else
            {
                Resize();

                int nextIndex = availableIndices.Pop();
                toReturn = instances[nextIndex];
                PostNew(toReturn);
            }

            return toReturn;
        }

        /// <summary>
        /// Make the object available again.
        /// </summary>
        /// <param name="toReclaim"></param>
        public void Reclaim(T toReclaim)
        {
            Debug.Assert(!availableIndices.Contains(toReclaim.PoolIndex));
            availableIndices.Push(toReclaim.PoolIndex);
            PostReclaim(toReclaim);
        }

        /// <summary>
        /// Double the size of the array.
        /// </summary>
        private void Resize()
        {
            int targetSize = 2 * instances.Length;
            T[] newInstances = new T[targetSize];

            for(int i = 0; i < instances.Length; i++)
            {
                newInstances[i] = instances[i];
            }

            for (int i = instances.Length; i < targetSize; i++)
            {
                newInstances[i] = Construct();
                newInstances[i].PoolIndex = i;
                availableIndices.Push(i);
            }

            instances = newInstances;
        }

    }
}
